Next: Assertions, Previous: Lists, Up: Top [Contents][Index]
The Common Lisp structure mechanism provides a
general way to define data types similar to C’s
struct types. A structure is a Lisp object
containing some number of slots, each of which can hold
any Lisp data object. Functions are provided for accessing and
setting the slots, creating or copying structure objects, and
recognizing objects of a particular structure type.
In true Common Lisp, each structure type is a new type distinct from all existing Lisp types. Since the underlying Emacs Lisp system provides no way to create new distinct types, this package implements structures as vectors (or lists upon request) with a special “tag” symbol to identify them.
The cl-defstruct form defines a new structure
type called name, with the specified
slots. (The slots may begin with a
string which documents the structure type.) In the simplest
case, name and each of the slots are
symbols. For example,
(cl-defstruct person name age sex)
defines a struct type called person that
contains three slots. Given a person object
p, you can access those slots by calling
(person-name p), (person-age
p), and (person-sex
p). You can also change these slots by
using setf on any of these place forms, for
example:
(cl-incf (person-age birthday-boy))
You can create a new person by calling
make-person, which takes keyword arguments
:name, :age, and :sex
to specify the initial values of these slots in the new
object. (Omitting any of these arguments leaves the
corresponding slot “undefined”, according to the
Common Lisp standard; in Emacs Lisp, such uninitialized slots
are filled with nil.)
Given a person, (copy-person
p) makes a new object of the same type
whose slots are eq to those of p.
Given any Lisp object x, (person-p
x) returns true if x looks like
a person, and false otherwise. (Again, in Common
Lisp this predicate would be exact; in Emacs Lisp the best it
can do is verify that x is a vector of the correct
length that starts with the correct tag symbol.)
Accessors like person-name normally check
their arguments (effectively using person-p) and
signal an error if the argument is the wrong type. This check
is affected by (optimize (safety …))
declarations. Safety level 1, the default, uses a somewhat
optimized check that will detect all incorrect arguments, but
may use an uninformative error message (e.g., “expected
a vector” instead of “expected a
person”). Safety level 0 omits all checks
except as provided by the underlying aref call;
safety levels 2 and 3 do rigorous checking that will always
print a descriptive error message for incorrect inputs. See
Declarations.
(setq dave (make-person :name "Dave" :sex 'male))
⇒ [cl-struct-person "Dave" nil male]
(setq other (copy-person dave))
⇒ [cl-struct-person "Dave" nil male]
(eq dave other)
⇒ nil
(eq (person-name dave) (person-name other))
⇒ t
(person-p dave)
⇒ t
(person-p [1 2 3 4])
⇒ nil
(person-p "Bogus")
⇒ nil
(person-p '[cl-struct-person counterfeit person object])
⇒ t
In general, name is either a name symbol or a list of a name symbol followed by any number of struct options; each slot is either a slot symbol or a list of the form ‘(slot-name default-value slot-options…)’. The default-value is a Lisp form that is evaluated any time an instance of the structure type is created without specifying that slot’s value.
Common Lisp defines several slot options, but the only one
implemented in this package is :read-only. A
non-nil value for this option means the slot
should not be setf-able; the slot’s value
is determined when the object is created and does not change
afterward.
(cl-defstruct person
(name nil :read-only t)
age
(sex 'unknown))
Any slot options other than :read-only are
ignored.
For obscure historical reasons, structure options take a different form than slot options. A structure option is either a keyword symbol, or a list beginning with a keyword symbol possibly followed by arguments. (By contrast, slot options are key-value pairs not enclosed in lists.)
(cl-defstruct (person (:constructor create-person)
(:type list)
:named)
name age sex)
The following structure options are recognized.
:conc-nameThe argument is a symbol whose print name is used as
the prefix for the names of slot accessor functions. The
default is the name of the struct type followed by a
hyphen. The option (:conc-name p-) would
change this prefix to p-. Specifying
nil as an argument means no prefix, so that
the slot names themselves are used to name the accessor
functions.
:constructorIn the simple case, this option takes one argument
which is an alternate name to use for the constructor
function. The default is
make-name, e.g.,
make-person. The above example changes this
to create-person. Specifying
nil as an argument means that no standard
constructor should be generated at all.
In the full form of this option, the constructor name
is followed by an arbitrary argument list. See Program
Structure, for a description of the format of Common
Lisp argument lists. All options, such as
&rest and &key, are
supported. The argument names should match the slot
names; each slot is initialized from the corresponding
argument. Slots whose names do not appear in the argument
list are initialized based on the
default-value in their slot descriptor. Also,
&optional and &key
arguments that don’t specify defaults take their
defaults from the slot descriptor. It is valid to include
arguments that don’t correspond to slot names;
these are useful if they are referred to in the defaults
for optional, keyword, or &aux arguments
that do correspond to slots.
You can specify any number of full-format
:constructor options on a structure. The
default constructor is still generated as well unless you
disable it with a simple-format :constructor
option.
(cl-defstruct
(person
(:constructor nil) ; no default constructor
(:constructor new-person
(name sex &optional (age 0)))
(:constructor new-hound (&key (name "Rover")
(dog-years 0)
&aux (age (* 7 dog-years))
(sex 'canine))))
name age sex)
The first constructor here takes its arguments
positionally rather than by keyword. (In official Common
Lisp terminology, constructors that work By Order of
Arguments instead of by keyword are called “BOA
constructors”. No, I’m not making this up.)
For example, (new-person "Jane" 'female)
generates a person whose slots are "Jane",
0, and female, respectively.
The second constructor takes two keyword arguments,
:name, which initializes the
name slot and defaults to
"Rover", and :dog-years, which
does not itself correspond to a slot but which is used to
initialize the age slot. The
sex slot is forced to the symbol
canine with no syntax for overriding it.
:copierThe argument is an alternate name for the copier
function for this type. The default is
copy-name. nil means
not to generate a copier function. (In this
implementation, all copier functions are simply synonyms
for copy-sequence.)
:predicateThe argument is an alternate name for the predicate
that recognizes objects of this type. The default is
name-p. nil means
not to generate a predicate function. (If the
:type option is used without the
:named option, no predicate is ever
generated.)
In true Common Lisp, typep is always able
to recognize a structure object even if
:predicate was used. In this package,
cl-typep simply looks for a function called
typename-p, so it will work for
structure types only if they used the default predicate
name.
:includeThis option implements a very limited form of
C++-style inheritance. The argument is the name of
another structure type previously created with
cl-defstruct. The effect is to cause the new
structure type to inherit all of the included
structure’s slots (plus, of course, any new slots
described by this struct’s slot descriptors). The
new structure is considered a
“specialization” of the included one. In
fact, the predicate and slot accessors for the included
type will also accept objects of the new type.
If there are extra arguments to the
:include option after the included-structure
name, these options are treated as replacement slot
descriptors for slots in the included structure, possibly
with modified default values. Borrowing an example from
Steele:
(cl-defstruct person name (age 0) sex)
⇒ person
(cl-defstruct (astronaut (:include person (age 45)))
helmet-size
(favorite-beverage 'tang))
⇒ astronaut
(setq joe (make-person :name "Joe"))
⇒ [cl-struct-person "Joe" 0 nil]
(setq buzz (make-astronaut :name "Buzz"))
⇒ [cl-struct-astronaut "Buzz" 45 nil nil tang]
(list (person-p joe) (person-p buzz))
⇒ (t t)
(list (astronaut-p joe) (astronaut-p buzz))
⇒ (nil t)
(person-name buzz)
⇒ "Buzz"
(astronaut-name joe)
⇒ error: "astronaut-name accessing a non-astronaut"
Thus, if astronaut is a specialization of
person, then every astronaut is
also a person (but not the other way
around). Every astronaut includes all the
slots of a person, plus extra slots that are
specific to astronauts. Operations that work on people
(like person-name) work on astronauts just
like other people.
:print-functionIn full Common Lisp, this option allows you to specify
a function that is called to print an instance of the
structure type. The Emacs Lisp system offers no hooks
into the Lisp printer which would allow for such a
feature, so this package simply ignores
:print-function.
:typeThe argument should be one of the symbols
vector or list. This tells
which underlying Lisp data type should be used to
implement the new structure type. Vectors are used by
default, but (:type list) will cause
structure objects to be stored as lists instead.
The vector representation for structure objects has the advantage that all structure slots can be accessed quickly, although creating vectors is a bit slower in Emacs Lisp. Lists are easier to create, but take a relatively long time accessing the later slots.
:namedThis option, which takes no arguments, causes a
characteristic “tag” symbol to be stored at
the front of the structure object. Using
:type without also using :named
will result in a structure type stored as plain vectors
or lists with no identifying features.
The default, if you don’t specify
:type explicitly, is to use named vectors.
Therefore, :named is only useful in
conjunction with :type.
(cl-defstruct (person1) name age sex)
(cl-defstruct (person2 (:type list) :named) name age sex)
(cl-defstruct (person3 (:type list)) name age sex)
(setq p1 (make-person1))
⇒ [cl-struct-person1 nil nil nil]
(setq p2 (make-person2))
⇒ (person2 nil nil nil)
(setq p3 (make-person3))
⇒ (nil nil nil)
(person1-p p1)
⇒ t
(person2-p p2)
⇒ t
(person3-p p3)
⇒ error: function person3-p undefined
Since unnamed structures don’t have tags,
cl-defstruct is not able to make a useful
predicate for recognizing them. Also, accessors like
person3-name will be generated but they will
not be able to do any type checking. The
person3-name function, for example, will
simply be a synonym for car in this case. By
contrast, person2-name is able to verify
that its argument is indeed a person2 object
before proceeding.
:initial-offsetThe argument must be a nonnegative integer. It
specifies a number of slots to be left
“empty” at the front of the structure. If the
structure is named, the tag appears at the specified
position in the list or vector; otherwise, the first slot
appears at that position. Earlier positions are filled
with nil by the constructors and ignored
otherwise. If the type :includes another
type, then :initial-offset specifies a
number of slots to be skipped between the last slot of
the included type and the first new slot.
Except as noted, the cl-defstruct facility of
this package is entirely compatible with that of Common Lisp.
The cl-defstruct package also provides a few
structure introspection functions.
This function returns the underlying data structure for
struct-type, which is a symbol. It returns
vector or list, or nil
if struct-type is not actually a structure.
This function returns a list of slot descriptors for
structure struct-type. Each entry in the list is
(name . opts), where name is the
name of the slot and opts is the list of slot
options given to defstruct. Dummy entries
represent the slots used for the struct name and that are
skipped to implement :initial-offset.
Return the offset of slot slot-name in
struct-type. The returned zero-based slot index
is relative to the start of the structure data type and is
adjusted for any structure name and :initial-offset slots.
Signal error if struct struct-type does not
contain slot-name.
Return the value of slot slot-name in
inst of struct-type.
struct and slot-name are symbols.
inst is a structure instance. This routine is
also a setf place. Can signal the same errors as
cl-struct-slot-offset.
Next: Assertions, Previous: Lists, Up: Top [Contents][Index]